local famBags = LibStub('AceAddon-3.0'):GetAddon('famBags')
local famBagsOptions = LibStub('AceAddon-3.0'):GetAddon('famBagsOptions')

famBagsOptions.Categories = {}

-- :::::::::::: LOCALS ::::::::::::: -------------------------------------------------------------------------

local L = famBags.L

local LSM = LibStub('LibSharedMedia-3.0', true)
local ACR = LibStub('AceConfigRegistry-3.0')
local ACD = LibStub('AceConfigDialog-3.0')
local AceGUI = LibStub('AceGUI-3.0')

local CATEGORIES_FRAME
local CATEGORIES_COUNT
local CURRENT_CATEGORY_LABEL
local CURRENT_CATEGORY_CONTAINER
local CURRENT_CATEGORY_TABS = {{},{}}
local CATEGORY_GROUP
local ADD_CRITERIA_DD
local ADD_CRITERIA_BT
local LOGIC_GROUP
local CRITERIAS_GROUP
local CURRENT_CATEGORY
local CRITERIAS = {}
local CURRENT_CRITERIA
local CURRENT_LOGIC_EDITING = -1
local CATEGORY_CONTAINER_COLOR = {
   ['all'] = '|cfff3cc20',
   ['bags'] = '|cffAAFFFF',
   ['bank'] = '|cff55FF55',
   ['none'] = '|cff666666',
}
local SECTION_DATA = {
   section = 'bags',
   db = famBags.db.profile.sections['bags'],
}

local PaneBackdrop  = {
   bgFile = 'Interface\\ChatFrame\\ChatFrameBackground',
   edgeFile = 'Interface\\Tooltips\\UI-Tooltip-Border',
   tile = true, tileSize = 16, edgeSize = 16,
   insets = { left = 3, right = 3, top = 5, bottom = 3 }
}

local function get_db()
   return famBags.db.profile.categoriesV2
end

local function get_value_index(arr, value)
   for i,v in ipairs(arr) do
      if v == value then return i end
   end
   return nil
end

local function CategoryOptions_OnClick(self, mousebutton)
   famBagsOptions.Categories:SelectCategory(self)
end

local function CategoriesConfigDialog_OnClose(widget)
--   for _,b in ipairs(CATEGORIES_FRAME.buttons) do
--      b:Hide()
--   end
--   wipe(CATEGORIES_FRAME.buttons)
--   famBagsOptions.Categories.ConfigDialog = nil
end

local function CurrentCategoryRename_OnEnterPressed(self, text, widget)
   text = strtrim(text)
   if #text < 3 or self:GetCategoryIndex(text) == CURRENT_CATEGORY then return true end
   famBagsOptions.Categories:RenameCategory(text)
   widget:SetText('')
   widget:ClearFocus()
end

local function CurrentCategoryContainer_OnValueChanged(value, widget)
   famBagsOptions.Categories:SetCategoryContainer(value)
end

local function CurrentCategoryMove_OnClick(mousebutton, widget)
   famBagsOptions.Categories:MoveCategory(widget.step)
end

local function CurrentCategoryDelete_OnClick(mousebutton)
   famBagsOptions.Categories:DeleteCategory()
end

local function CurrentCriteria_OnValueChanged(value, widget)
   CURRENT_CRITERIA = value
end

local function AddCriteria_OnClick(mousebutton)
   famBagsOptions.Categories:AddNewCriteria(CURRENT_CRITERIA)
end

local function CategoryTab_OnClick(button)
   famBagsOptions.Categories:SelectCategoryTab(button.index)
end

local function Type_OnValueChanged(value, widget, checked)
   local new = (widget.db.type ~= value)
   widget.db.type = value
   widget.db.options = widget.db.options or {}
   widget.subTypesDropDown:SetList({ [0] = L['.: All :.'], unpack(famBags.Subtypes[value]) })
   if new then
      wipe(widget.db.options)
      widget.db.options[0] = 1
      widget.subTypesDropDown:SetItemValue(0, true)
   else
      for i in pairs(widget.db.options) do
         widget.subTypesDropDown:SetItemValue(i, true)
      end
   end
   famBags.Classification:ResetBuiltCriterias()
   famBags:LayoutBagsButtons()
end

local function CriteriaOptions_OnValueChanged(value, widget, checked)
   local all = 0
   if famBags.Criterias[widget.db.index].stringKeys then
      all = '0'
   end
   if checked then
      if value == all then
         wipe(widget.db.options)
         for _,item in widget.pullout:IterateItems() do
            if item.userdata.value ~= all and item.SetValue then
               item:SetValue(nil)
            end
         end
         widget.pullout:Close()
      else
         widget.db.options[all] = nil
         widget:SetItemValue(all, nil)
      end
      widget.db.options[value] = 1

   elseif rawget(widget.db.options, value) then
      widget.db.options[value] = nil
      if not next(widget.db.options) then
         widget.db.options[all] = 1
         widget:SetItemValue(all, true)
      end
   end
   famBags.Classification:ResetBuiltCriterias()
   famBags:LayoutBagsButtons()
end

local function CriteriaComparer_OnValueChanged(value, widget, checked)
   widget.db.comparer = value
   famBags.Classification:ResetBuiltCriterias()
   famBags:LayoutBagsButtons()
end

local function CriteriaValue_OnEnterPressed(self, text, widget)
   widget.db.value = text
   famBags.Classification:ResetBuiltCriterias()
   famBags:LayoutBagsButtons()
end

local function CriteriaMove_OnClick(mousebutton, widget)
   famBagsOptions.Categories:MoveCriteria(widget.index, widget.step)
end

local function CriteriaDelete_OnClick(mousebutton, widget)
   famBagsOptions.Categories:DeleteCriteria(widget.index)
end

local function CriteriaLogic_OnClick(mousebutton, widget)
   famBagsOptions.Categories:SetLogicEditing(true)
end

local function CriteriaLogic_OnEnterPressed(self, text, widget)
   widget.db.logic = strtrim(text)
   famBagsOptions.Categories:SetLogicEditing(nil)
   famBags.Classification:ResetBuiltCriterias()
   famBags:LayoutBagsButtons()
end

local function CriteriaLogic_OnEscapePressed(self, text, widget)
   famBagsOptions.Categories:SetLogicEditing(nil)
end

local function CriteriaLogic_OnTextChanged(widget, event, text)
   local db = widget.db
   if not (db.criterias and text) or strtrim(text) == '' then return end
   local c = #db.criterias
   local t = text
   local range = '['..strlower(famBags.Symbols[1])..'-'..strlower(famBags.Symbols[#famBags.Symbols])..']'
   t = string.gsub(t, '^('..range..')([%p%s%c])', function(s1, s2) return strupper(s1)..s2 end)
   t = string.gsub(t, '([%p%s%c])('..range..')$', function(s1, s2) return s1..strupper(s2) end)
   t = string.gsub(t, '([%p%s%c])('..range..')([%p%s%c])', function(s1, s2, s3) return s1..strupper(s2)..s3 end)
   t = string.gsub(t, '%w+', function(s)
      if not tContains(famBags.Symbols, s) then
         return strlower(s)
      end
      return s
   end)
   local p = widget.editbox:GetCursorPosition()
   widget:SetText(t)
   widget.editbox:SetCursorPosition(p)
end

local function build_criterias()
   for _,c in ipairs(famBags.Criterias) do
      table.insert(CRITERIAS, c.name)
   end
end


-- :::::::::::: famBagsOptions.Categories ::::::::::::: -------------------------------------------------------------------------

function famBagsOptions.Categories:ShowCategoriesConfig()
   if self.ConfigDialog and self.ConfigDialog:IsVisible() then return end
   self.ConfigDialog = self.ConfigDialog or self:CreateConfigDialog()
   self.ConfigDialog:Show()
end

function famBagsOptions.Categories:GetCategoryDB(category)
   local db = get_db()
   for i,d in ipairs(db) do
      if d.category == category then
         return d
      end
   end
   return nil
end

function famBagsOptions.Categories:GetCategoryOptionsData(cat)
   if not cat then return end
   local index, category, button
   if type(cat) == 'number' then
      if cat <= 0 or cat > #CATEGORIES_FRAME.buttons then return end
      return cat, CATEGORIES_FRAME.buttons[cat].category, CATEGORIES_FRAME.buttons[cat]
   elseif type(cat) == 'string' then
      for _,b in ipairs(CATEGORIES_FRAME.buttons) do
         if b.category == cat then
            return b.index, b.category, b
         end
      end
   else
      return cat.index, cat.category, cat
   end
end

function famBagsOptions.Categories:GetCategoryIndex(category)
   category = strupper(category)
   local db = get_db()
   for i, c in ipairs(db) do
      if strupper(c.category) == category then return i end
   end
   return nil
end

function famBagsOptions.Categories:CreateNewCategory(name, edit)
   local db = get_db()
   name = strtrim(name)
   if #name < 3 or self:GetCategoryIndex(name) then return true end

   if edit then edit:SetText('') end
   local new_cat = {
      category = name,
      logic = '',
      container = 'all',
   }
   table.insert(db, new_cat)

   local dbs = famBags.db.profile.sections
   dbs[name] = {}
   dbs[name].bg = famBags:GetBGDefaults()
   dbs[name].title = famBags:GetTitleDefaults()
   --SECTIONS[name] = name
 --   local section_name = L['Sections']..' -> '..c
 --   if not tContains(APP_COPYFROM, section_name) then
 --      table.insert(APP_COPYFROM, section_name)
 --      APP_COPYFROM_DB[section_name] = dbs[name].bg
 --      table.insert(APP_COPYFROM, section_name..' -> '..L['Title'])
 --      APP_COPYFROM_DB[section_name..' -> '..L['Title']] = dbs[name].title.bg
 --      table.sort(APP_COPYFROM)
 --   end

   famBags:BuildSortedCategories()
   self:AddCategoryButton(CATEGORIES_FRAME, name, new_cat)
   self:SelectCategoryTab(1)
   self:DisableGroup(CATEGORY_GROUP, false)
   CURRENT_CATEGORY_TABS[1].tab:Enable()
   CURRENT_CATEGORY_TABS[2].tab:Enable()
   self:SelectCategory(#db)
   famBags.Classification:ResetBuiltCriterias()
   famBags:LayoutBagsButtons()
end

function famBagsOptions.Categories:RenameCategory(newname)
   local dbg = get_db()
   local db = dbg[CURRENT_CATEGORY]
   local dbs = famBags.db.profile.sections
   famBagsOptions.Sections[db.category] = nil
   dbs[newname] = dbs[db.category]
   dbs[db.category] = nil

   local name = L['Sections']..' -> '..db.category
   local index = get_value_index(famBagsOptions.AppearanceCopyFrom, name)
   if index and index > 0 then
      local cname = L['Sections']..' -> '..newname
      famBagsOptions.AppearanceCopyFrom[index] = cname
      famBagsOptions.AppearanceCopyFrom_DB[name] = nil
      famBagsOptions.AppearanceCopyFrom_DB[cname] = dbs[newname].bg

      name = name..' -> '..L['Title']
      index = get_value_index(famBagsOptions.AppearanceCopyFrom, name)
      if index and index > 0 then
         cname = cname..' -> '..L['Title']
         famBagsOptions.AppearanceCopyFrom[index] = cname
         famBagsOptions.AppearanceCopyFrom_DB[name] = nil
         famBagsOptions.AppearanceCopyFrom_DB[cname] = dbs[newname].title.bg
      end
      table.sort(famBagsOptions.AppearanceCopyFrom)
   end

   for _,cat in ipairs(dbg) do
      if cat.criterias then
         for _,c in ipairs(cat.criterias) do
            if c.index == FAMBAGSCRITERIA_CATEGORIES then
               if rawget(c.options, db.category) then
                  c.options[db.category], c.options[newname] = nil, c.options[db.category]
               end
            end
         end
      end
   end

   db.category = newname
   famBagsOptions.Sections[db.category] = db.category
   local b = CATEGORIES_FRAME.buttons[CURRENT_CATEGORY]
   b.category = newname
   b:SetText((CATEGORY_CONTAINER_COLOR[db.container] or '')..newname)
   CURRENT_CATEGORY_LABEL:SetText((CATEGORY_CONTAINER_COLOR[db.container] or '')..newname)
   self:UpdateSectionConfig(db.category)
   famBags:BuildSortedCategories()
   famBags.Classification:ResetBuiltCriterias()
   famBags:LayoutBagsButtons()
end

function famBagsOptions.Categories:SetCategoryContainer(container)
   local db = get_db()[CURRENT_CATEGORY]
   db.container = container
   local b = CATEGORIES_FRAME.buttons[CURRENT_CATEGORY]
   b:SetText((CATEGORY_CONTAINER_COLOR[db.container] or '')..db.category)
   CURRENT_CATEGORY_LABEL:SetText((CATEGORY_CONTAINER_COLOR[db.container] or '')..db.category)
   famBags:BuildSortedCategories()
   famBags.Classification:ResetBuiltCriterias()
   famBags:LayoutBagsButtons()
end

function famBagsOptions.Categories:MoveCategory(step)
   local db = get_db()
   local i, j = CURRENT_CATEGORY, CURRENT_CATEGORY + step
   if j < 1 or j > #db then return end

   db[i], db[j] = db[j], db[i]
   CATEGORIES_FRAME.buttons[i], CATEGORIES_FRAME.buttons[j] =
      CATEGORIES_FRAME.buttons[j], CATEGORIES_FRAME.buttons[i]

   for i,b in ipairs(CATEGORIES_FRAME.buttons) do
      if i > 1 then
         b:SetPoint('TOPLEFT', CATEGORIES_FRAME.buttons[i-1], 'BOTTOMLEFT', 0, 0)
         b:SetPoint('TOPRIGHT', CATEGORIES_FRAME.buttons[i-1], 'BOTTOMRIGHT', 0, 0)
      else
         b:SetPoint('TOPRIGHT', 0, 0)
         b:SetPoint('TOPLEFT', 0, 0)
      end
      b.index = i
   end

   self:SelectCategory(j)
   famBags:BuildSortedCategories()
   famBags.Classification:ResetBuiltCriterias()
   famBags:LayoutBagsButtons()
end

function famBagsOptions.Categories:DeleteCategory()
   local db = get_db()
   CATEGORIES_FRAME.buttons[CURRENT_CATEGORY]:Hide()
   local category_name = db[CURRENT_CATEGORY].category
   table.remove(db, CURRENT_CATEGORY)
   table.remove(CATEGORIES_FRAME.buttons, CURRENT_CATEGORY)
   local height = 0
   for i,b in ipairs(CATEGORIES_FRAME.buttons) do
      if i > 1 then
         b:SetPoint('TOPLEFT', CATEGORIES_FRAME.buttons[i-1], 'BOTTOMLEFT', 0, 0)
         b:SetPoint('TOPRIGHT', CATEGORIES_FRAME.buttons[i-1], 'BOTTOMRIGHT', 0, 0)
      else
         b:SetPoint('TOPRIGHT', 0, 0)
         b:SetPoint('TOPLEFT', 0, 0)
         height = b:GetHeight()
      end
      b.index = i
   end

   famBagsOptions.Sections[category_name] = nil

   local name = L['Sections']..' -> '..category_name
   local index = get_value_index(famBagsOptions.AppearanceCopyFrom, name)
   if index and index > 0 then
      table.remove(famBagsOptions.AppearanceCopyFrom, index)
      famBagsOptions.AppearanceCopyFrom_DB[name] = nil

      name = name..' -> '..L['Title']
      index = get_value_index(famBagsOptions.AppearanceCopyFrom, name)
      if index and index > 0 then
         table.remove(famBagsOptions.AppearanceCopyFrom, index)
         famBagsOptions.AppearanceCopyFrom_DB[name] = nil
      end
      --table.sort(famBagsOptions.AppearanceCopyFrom)
      if famBagsOptions.CurrentAppearanceCopyFrom > #famBagsOptions.AppearanceCopyFrom then
         famBagsOptions.CurrentAppearanceCopyFrom = #famBagsOptions.AppearanceCopyFrom
      end
   end

   for _,cat in ipairs(db) do
      if cat.criterias then
         for _,c in ipairs(cat.criterias) do
            if c.index == FAMBAGSCRITERIA_CATEGORIES then
               if rawget(c.options, category_name) then
                  c.options[category_name] = nil
               end
            end
         end
      end
   end

   famBags.db.profile.sections[category_name] = nil
   CATEGORIES_FRAME.content:SetHeight(height * #CATEGORIES_FRAME.buttons)
   CATEGORIES_FRAME:FixScroll()
   self:SelectCategory(CURRENT_CATEGORY, true)
   famBags:BuildSortedCategories()
   famBags.Classification:ResetBuiltCriterias()
   famBags:LayoutBagsButtons()
end

function famBagsOptions.Categories:SelectCategory(cat, force)
   local index, category, button = self:GetCategoryOptionsData(cat)
   if index == CURRENT_CATEGORY and not force then return end
   local db = get_db()
   c = #db
   if CURRENT_CATEGORY and CURRENT_CATEGORY > 0 and CURRENT_CATEGORY <= c then
      local oldbutton = CATEGORIES_FRAME.buttons[CURRENT_CATEGORY]
      oldbutton.selected = nil
      oldbutton:UnlockHighlight()
   end
   CURRENT_CATEGORY = (index and index <= c) and index or #db
   if CURRENT_CATEGORY > 0 then
      local button = CATEGORIES_FRAME.buttons[CURRENT_CATEGORY]
      button.selected = true
      button:LockHighlight()
      db = db[CURRENT_CATEGORY]
      CURRENT_CATEGORY_LABEL:SetText((CATEGORY_CONTAINER_COLOR[db.container] or '')..db.category)
      CURRENT_CATEGORY_CONTAINER:SetValue(db.container)
   else
      CURRENT_CATEGORY_LABEL:SetText('')
      CURRENT_CATEGORY_CONTAINER:SetValue(nil)
   end
   self:UpdateCurrentCategory()
end

function famBagsOptions.Categories:SelectCategoryTab(tab)
   local theother = (tab == 1) and 2 or 1
   CURRENT_CATEGORY_TABS[theother].container._hidden = true
   CURRENT_CATEGORY_TABS[tab].container._hidden = nil
   CURRENT_CATEGORY_TABS[theother].tab:UnlockHighlight()
   CURRENT_CATEGORY_TABS[tab].tab:LockHighlight()
   ADD_CRITERIA_DD:SetDisabled(tab == 2)
   ADD_CRITERIA_BT:SetDisabled(tab == 2)
   CATEGORY_GROUP:DoLayout()
end

function famBagsOptions.Categories:UpdateCurrentCategory()
   CRITERIAS_GROUP:ReleaseChildren()
   if CURRENT_CATEGORY > 0 then
      local db = get_db()[CURRENT_CATEGORY]
      if db.criterias then
         for i,c in ipairs(db.criterias) do
            famBagsOptions.Categories:RepresentCriteria(i, c)
         end
      end
      self:UpdateSectionConfig(db.category)
   else
      self:SelectCategoryTab(1)
      self:DisableGroup(CATEGORY_GROUP, true)
      CURRENT_CATEGORY_TABS[1].tab:Disable()
      CURRENT_CATEGORY_TABS[2].tab:Disable()
   end
   self:SetLogicEditing(false)
   CATEGORY_GROUP:DoLayout()
end

function famBagsOptions.Categories:AddNewCriteria(index)
   local db = get_db()[CURRENT_CATEGORY]
   db.criterias = db.criterias or {}
   local dbindex = #db.criterias + 1
   local new_criteria = {
      index = index,
      symbol = famBags.Symbols[dbindex],
   }
   if index > 1 then
      if famBags.Criterias[index].valueType then
         new_criteria.comparer = 1
      elseif famBags.Criterias[index].stringKeys then
         new_criteria.options = { ['0'] = 1 }
      else
         new_criteria.options = { [0] = 1 }
      end
   end
   table.insert(db.criterias, new_criteria)
   if not db.logic or db.logic == '' then
      db.logic = new_criteria.symbol
   else
      local exists = db.logic == new_criteria.symbol or
                     string.find(db.logic, '^'..new_criteria.symbol..'[%p%s%c]') or
                     string.find(db.logic, '[%p%s%c]'..new_criteria.symbol..'$') or
                     string.find(db.logic, '[%p%s%c]'..new_criteria.symbol..'[%p%s%c]')
      if not exists then
         db.logic = db.logic..' and '..new_criteria.symbol
      end
   end
   self:RepresentCriteria(dbindex, new_criteria)
   self:SetLogicEditing(false)
   famBags.Classification:ResetBuiltCriterias()
   famBags:LayoutBagsButtons()
end

function famBagsOptions.Categories:UpdateSectionConfig(category, force)
   local appName = 'famBagsStandaloneSectionConfig_'..category
   if not ACR.tables[appName] or force then
      ACR:RegisterOptionsTable(appName, famBagsOptions:GetSectionsOptions(category, self.UpdateSectionConfig))
   end
   ACD:Open(appName, CURRENT_CATEGORY_TABS[2].container)
end

function famBagsOptions.Categories:RepresentCriteria(index, db)
   --dump('db', db)
   self:CreateLabel(CRITERIAS_GROUP, '|cff55FF55'..db.symbol..' =', GameFontNormalLarge, 40, true, 0, -13)
   local criteria = famBags.Criterias[db.index]
   if db.index == 1 then
      local t = self:CreateDropDown(CRITERIAS_GROUP, criteria.name, criteria.options, nil, 120, nil, 0, nil, Type_OnValueChanged)
      t.db = db

      local s = self:CreateDropDown(CRITERIAS_GROUP, ' ', nil, true, 380, nil, nil, nil, CriteriaOptions_OnValueChanged)
      s.db = db
      t.subTypesDropDown = s

      if db.type then
         t:SetValue(db.type)
         Type_OnValueChanged(db.type, t)
      end
   elseif not criteria.valueType then
      local list
      if criteria.stringKeys then
         list = {}
         if not criteria.noAll then
            list['0'] = L['.: All :.']
         end
         for i,o in pairs(criteria.options) do
            i = criteria.selfKeys and i or o
            list[i] = o
         end
      else
         list = famBags:CopyTable(criteria.options)
         if not criteria.noALL then
            list[0] = L['.: All :.']
         end
         -- A terrible hack? I know.. Blizzard named INVTYPE_CHEST, INVTYPE_ROBE and
         -- INVTYPE_SHIELD, INVTYPE_WEAPONOFFHAND the same.
         if db.index == FAMBAGSCRITERIA_EQUIPSLOT then
            list[7] = list[7]..' ('..L['Robe']..')'
            list[17] = list[17]..' ('..L['Shields']..')'
            list[20] = list[20]..' ('..L['Weapon']..')'
         end
      end
      local o = self:CreateDropDown(CRITERIAS_GROUP, criteria.name, list, true, 500, nil, nil, nil, CriteriaOptions_OnValueChanged)
      o.db = db
      if db.index == FAMBAGSCRITERIA_CATEGORIES then
         o:SetItemDisabled(get_db()[CURRENT_CATEGORY].category, true)
      end
      self:UpdateCriteriaOptions(o)
   else
      local o = self:CreateDropDown(CRITERIAS_GROUP, criteria.name, criteria.options, nil, 120, nil, 0, nil, CriteriaComparer_OnValueChanged)
      o.db = db
      o:SetValue(db.comparer)

      local t = self:CreateEditBox(CRITERIAS_GROUP, ' ', 380, nil, nil, nil, CriteriaValue_OnEnterPressed)
      t.db = db
      t:SetText(db.value)
   end
   local b = self:CreateArrowButton(CRITERIAS_GROUP, 'Up', nil, 0, -3, CriteriaMove_OnClick)
   b.index = index
   b.step = -1
   b = self:CreateArrowButton(CRITERIAS_GROUP, 'Down', nil, 0, -3, CriteriaMove_OnClick)
   b.index = index
   b.step = 1
   b = self:CreateButton(CRITERIAS_GROUP, L['Delete'], 100, nil, 0, -5, CriteriaDelete_OnClick)
   b.index = index
end

function famBagsOptions.Categories:UpdateCriteriaOptions(widget)
   widget.db.options = widget.db.options or ((widget.db.stringKeys) and { ['0'] = 1 } or  { [0] = 1 })
   for i in pairs(widget.db.options) do
      widget:SetItemValue(i, true)
   end
end

function famBagsOptions.Categories:MoveCriteria(index, step)
   local db = get_db()[CURRENT_CATEGORY].criterias
   if (step == 1 and index == #db) or (step == -1 and index == 1) then return end
   self:SwapLogicSymbols(get_db()[CURRENT_CATEGORY], db[index].symbol, db[index + step].symbol)
   db[index].symbol, db[index + step].symbol = db[index + step].symbol, db[index].symbol
   db[index], db[index + step] = db[index + step], db[index]
   self:UpdateCurrentCategory()
   famBags.Classification:ResetBuiltCriterias()
   famBags:LayoutBagsButtons()
end

function famBagsOptions.Categories:DeleteCriteria(index)
   local db = get_db()[CURRENT_CATEGORY]
   table.remove(db.criterias, index)
   for i,c in ipairs(db.criterias) do
      if c.symbol ~= famBags.Symbols[i] then
         self:SwapLogicSymbols(db, c.symbol, famBags.Symbols[i])
         c.symbol = famBags.Symbols[i]
      end
   end
   self:UpdateCurrentCategory()
   famBags.Classification:ResetBuiltCriterias()
   famBags:LayoutBagsButtons()
end

function famBagsOptions.Categories:SetLogicEditing(edit)
   local db = get_db()[CURRENT_CATEGORY]
   if edit then
      local e
      if edit ~= CURRENT_LOGIC_EDITING then
         LOGIC_GROUP:ReleaseChildren()
         e = self:CreateEditBox(LOGIC_GROUP, nil, 'relative', nil, 5, nil, CriteriaLogic_OnEnterPressed)
         e.relWidth = 0.974
         e:SetHeight(28)
         e.editbox:SetPoint('TOPLEFT',e.frame,'TOPLEFT',7,3)
         e.db = db
         e:SetCallback('OnTextChanged', CriteriaLogic_OnTextChanged)
         e.editbox:SetScript('OnEscapePressed', CriteriaLogic_OnEscapePressed)
      else
         e = LOGIC_GROUP.children[1]
      end
      e:SetText(db.logic)
      e:SetFocus()
   else
      local t = (db) and self:FormatLogic(db.logic, db) or ''
      if edit ~= CURRENT_LOGIC_EDITING then
         LOGIC_GROUP:ReleaseChildren()
         local l = self:CreateLabel(LOGIC_GROUP, t, GameFontNormalLarge, 'relative', nil, 0, -3)
         l.relWidth = 0.93
         l.label:SetHeight(28)
         l:SetColor(1, 0.3, 0.3)
         l.label:SetJustifyH('CENTER')
         l.label:SetJustifyV('TOP')

         local b = self:CreateButton(LOGIC_GROUP, '...', 'relative', nil, 0, 0, CriteriaLogic_OnClick)
         b.relWidth = 0.055
      else
         local l = LOGIC_GROUP.children[1]
         l:SetText(t)
      end
   end
   CURRENT_LOGIC_EDITING = edit
end

function famBagsOptions.Categories:FormatLogic(text, db)
   if not (db.criterias and text) or text == '' then return end
   local c = #db.criterias
   if c == 0 then return text end
   local t = text
   local range = famBags.Symbols[1]
   if c > 1 then
      range = '['..range..'-'..famBags.Symbols[c]..']'
   end
   if #t == 1 then
      local i = get_value_index(famBags.Symbols, t)
      if i and i > 0 and i <= c then
         return '|cff55FF55'..t..'|r'
      else
         return t
      end
   end
   t = string.gsub(t, '^('..range..')([%p%s%c])', '|cff55FF55%1|r%2')
   t = string.gsub(t, '([%p%s%c])('..range..')$', '%1|cff55FF55%2|r')
   t = string.gsub(t, '([%p%s%c])('..range..')([%p%s%c])', '%1|cff55FF55%2|r%3')
   t = string.gsub(t, '([%p%s])(and)([%p%s])', '%1|cffFFFF88%2|r%3')
   t = string.gsub(t, '([%p%s])(or)([%p%s])', '%1|cffFFFF88%2|r%3')
   t = string.gsub(t, '([%p%s])(not)([%p%s])', '%1|cffFFFF88%2|r%3')
   t = string.gsub(t, '^(not)([%p%s])', '|cffFFFF88%1|r%2')
   t = string.gsub(t, '(&&)', '|cffFFFF88%1|r')
   t = string.gsub(t, '(||||)', '|cffFFFF88%1|r')
   t = string.gsub(t, '(!)', '|cffFFFF88%1|r')
   t = string.gsub(t, '%(+', '|cffAAFFFF%1|r')
   t = string.gsub(t, '%)+', '|cffAAFFFF%1|r')
   return t
end

function famBagsOptions.Categories:SwapLogicSymbols(db, s1, s2)
   local t = db.logic
   if not t or t == '' then
      return
   elseif t == s1 then
      db.logic = s2
      return
   end
   t = string.gsub(t, '^('..s1..')([%p%s%c])', '@%2')
   t = string.gsub(t, '([%p%s%c])('..s1..')$', '%1@')
   t = string.gsub(t, '([%p%s%c])('..s1..')([%p%s%c])', '%1@%3')

   t = string.gsub(t, '^('..s2..')([%p%s%c])', s1..'%2')
   t = string.gsub(t, '([%p%s%c])('..s2..')$', '%1'..s1)
   t = string.gsub(t, '([%p%s%c])('..s2..')([%p%s%c])', '%1'..s1..'%3')

   t = string.gsub(t, '^(@)([%p%s%c])', s2..'%2')
   t = string.gsub(t, '([%p%s%c])(@)$', '%1'..s2)
   t = string.gsub(t, '([%p%s%c])(@)([%p%s%c])', '%1'..s2..'%3')

   db.logic = t
end


-- :::::::::::: GUI related :::::::::::::: --------------------------------------------------------------

function famBagsOptions.Categories:CreateConfigDialog()
   CATEGORIES_COUNT = 0
   CURRENT_CATEGORY = nil
   local f = AceGUI:Create('Frame')
   _G['famBagsCategoriesConfigDialogFrame'] = f.frame
   table.insert(UISpecialFrames,'famBagsCategoriesConfigDialogFrame')
   f:SetTitle('famBags: '..L['Categories'])
   f:SetWidth(1000)
   f:SetHeight(700)
   f.frame:SetMinResize(1000, 700)
   --f:SetStatusText('Status Bar')
   f:SetLayout('ExtendedFlow')
   f:SetCallback('OnClose', CategoriesConfigDialog_OnClose)
   self:CreateEditBox(f, L['Add new category'], 200, nil, nil, nil, self.CreateNewCategory)

   CATEGORIES_FRAME = AceGUI:Create('ScrollFrame')
   CATEGORIES_FRAME:SetWidth(200)
   CATEGORIES_FRAME.scrollframe:SetPoint('TOPLEFT', 10, -10)
   CATEGORIES_FRAME.scrollframe:SetPoint('BOTTOMRIGHT', -10, 10)
   CATEGORIES_FRAME.scrollbar:SetPoint('TOPLEFT', CATEGORIES_FRAME.scrollframe, 'TOPRIGHT', -2, -13)
   CATEGORIES_FRAME.scrollbar:SetPoint('BOTTOMLEFT', CATEGORIES_FRAME.scrollframe, 'BOTTOMRIGHT', -2, 22)
   CATEGORIES_FRAME.height = 'fill'
   CATEGORIES_FRAME.newLine = true
   self:SetGroupBackdrop(CATEGORIES_FRAME)
   CATEGORIES_FRAME:SetLayout(nil)

   for _,c in ipairs(famBags.SortedCategories.all) do
      self:AddCategoryButton(CATEGORIES_FRAME, c)
   end

   f:AddChild(CATEGORIES_FRAME)

   CATEGORY_GROUP = AceGUI:Create('SimpleGroup')
   CATEGORY_GROUP.width = 'fill'
   CATEGORY_GROUP.height = 'fill'
   CATEGORY_GROUP.alignoffset = 0
   CATEGORY_GROUP.forceRow = true
   CATEGORY_GROUP:SetLayout('Flow')

   local g = AceGUI:Create('SimpleGroup')
   g:SetLayout('ExtendedFlow')
   g.width = 'fill'

   local b = self:CreateArrowButton(g, 'Up', nil, nil, nil, CurrentCategoryMove_OnClick)
   b.step = -1
   self:CreateEditBox(g, L['Rename'], 200, nil, 50, nil, CurrentCategoryRename_OnEnterPressed)
   CURRENT_CATEGORY_CONTAINER = self:CreateDropDown(g, L['Enabled on'],
      {
        ['all']=CATEGORY_CONTAINER_COLOR['all']..L['All containers'],
        ['bags']=CATEGORY_CONTAINER_COLOR['bags']..L['Bags'],
        ['bank']=CATEGORY_CONTAINER_COLOR['bank']..L['Bank'],
        ['none']=CATEGORY_CONTAINER_COLOR['none']..L['None'],
      },
      nil, 200, nil, 50, nil, CurrentCategoryContainer_OnValueChanged)

   b = self:CreateArrowButton(g, 'Down', true, nil, nil, CurrentCategoryMove_OnClick)
   b.step = 1
   self:CreateButton(g, L['Delete'], 200, nil, 50, nil, CurrentCategoryDelete_OnClick)

   self:CreateHeader(g)

   CURRENT_CATEGORY_LABEL = self:CreateLabel(g, '', GameFontNormalLarge, 200, true, 10, -13)
   if #CRITERIAS == 0 then
      build_criterias()
   end
   CURRENT_CRITERIA = 1
   ADD_CRITERIA_DD = self:CreateDropDown(g, L['Criterias'], CRITERIAS, nil, 200, nil, 10, nil, CurrentCriteria_OnValueChanged)
   ADD_CRITERIA_DD:SetValue(CURRENT_CRITERIA)
   ADD_CRITERIA_BT = self:CreateButton(g, L['Add'], 100, nil, 5, -6, AddCriteria_OnClick)

   CATEGORY_GROUP:AddChild(g)

   local tab = AceGUI:Create('SimpleGroup')
   tab.alignoffset = 0
   tab.width = 'fill'
   tab.height = 'fill'
   tab:SetLayout('ExtendedFlow')

   CURRENT_CATEGORY_TABS[1].tab = _G['famBagsCategoriesCurrentCategoryTab1'] or CreateFrame("Button", 'famBagsCategoriesCurrentCategoryTab1', CATEGORY_GROUP.frame, "OptionsFrameTabButtonTemplate")
   CURRENT_CATEGORY_TABS[1].tab:SetParent(CATEGORY_GROUP.frame)
   CURRENT_CATEGORY_TABS[1].tab:SetText(L['Criterias'])
   CURRENT_CATEGORY_TABS[1].tab:SetNormalFontObject(GameFontNormal)
   CURRENT_CATEGORY_TABS[1].tab:SetHighlightFontObject(GameFontHighlight)
   CURRENT_CATEGORY_TABS[1].tab.index = 1
   CURRENT_CATEGORY_TABS[1].tab:SetScript('OnClick', CategoryTab_OnClick)
   CURRENT_CATEGORY_TABS[2].tab = _G['famBagsCategoriesCurrentCategoryTab2'] or CreateFrame("Button", 'famBagsCategoriesCurrentCategoryTab2', CATEGORY_GROUP.frame, "OptionsFrameTabButtonTemplate")
   CURRENT_CATEGORY_TABS[2].tab:SetParent(CATEGORY_GROUP.frame)
   CURRENT_CATEGORY_TABS[2].tab:SetText(L['Sections'])
   CURRENT_CATEGORY_TABS[2].tab:SetNormalFontObject(GameFontNormal)
   CURRENT_CATEGORY_TABS[2].tab:SetHighlightFontObject(GameFontHighlight)
   CURRENT_CATEGORY_TABS[2].tab.index = 2
   CURRENT_CATEGORY_TABS[2].tab:SetScript('OnClick', CategoryTab_OnClick)
   CURRENT_CATEGORY_TABS[1].tab:SetPoint('BOTTOMRIGHT', CURRENT_CATEGORY_TABS[2].tab, 'BOTTOMLEFT', 10, 0)
   CURRENT_CATEGORY_TABS[2].tab:SetPoint('BOTTOMRIGHT', tab.frame, 'TOPRIGHT', 0, 0)

   g = AceGUI:Create('SimpleGroup')
   CURRENT_CATEGORY_TABS[1].container = g
   --g.alignoffset = 0
   self:SetGroupBackdrop(g)
   g.width = 'fill'
   g.height = 'fill'
   g.content:SetPoint('TOPLEFT', 5, -5)
   g.content:SetPoint('BOTTOMRIGHT', -5, 5)
   g:SetLayout('ExtendedFlow')

   LOGIC_GROUP = AceGUI:Create('SimpleGroup')
   LOGIC_GROUP.width = 'fill'
   self:SetGroupBackdrop(LOGIC_GROUP)
   LOGIC_GROUP:SetLayout('ExtendedFlow')
   CURRENT_LOGIC_EDITING = -1
   g:AddChild(LOGIC_GROUP)

   local g1 = AceGUI:Create('SimpleGroup')
   g1.width = 'fill'
   g1.height = 'fill'
   self:SetGroupBackdrop(g1)
   g1.content:SetPoint('TOPLEFT', 10, -5)
   g1.content:SetPoint('BOTTOMRIGHT', -5, 5)
   g1:SetLayout('Flow')

   CRITERIAS_GROUP = AceGUI:Create('ScrollFrame')
   CRITERIAS_GROUP.width = 'fill'
   CRITERIAS_GROUP.height = 'fill'
   CRITERIAS_GROUP:SetLayout('ExtendedFlow')

   g1:AddChild(CRITERIAS_GROUP)

   g:AddChild(g1)

   tab:AddChild(g)

   g = AceGUI:Create('SimpleGroup')
   CURRENT_CATEGORY_TABS[2].container = g
   self:SetGroupBackdrop(g)
   g.width = 'fill'
   g.height = 'fill'
   g.content:SetPoint('TOPLEFT', 5, -5)
   g.content:SetPoint('BOTTOMRIGHT', -5, 5)

   tab:AddChild(g)

   CURRENT_CATEGORY_TABS[1].container._hidden = true
   CURRENT_CATEGORY_TABS[2].container._hidden = true

   CATEGORY_GROUP:AddChild(tab)

   f:AddChild(CATEGORY_GROUP)

   self:SelectCategoryTab(1)

   if #famBags.SortedCategories.all == 0 then
      self:SelectCategoryTab(1)
      self:DisableGroup(CATEGORY_GROUP, true)
      CURRENT_CATEGORY_TABS[1].tab:Disable()
      CURRENT_CATEGORY_TABS[2].tab:Disable()
   else
      self:SelectCategory(1)
   end

   CATEGORY_GROUP:DoLayout()

   return f
end

function famBagsOptions.Categories:CreateEditBox(container, label, width, newLine, xoffset, alignoffset, enterpressed)
   local e = AceGUI:Create('EditBox')
   e:SetText('')
   e.newLine = newLine
   e.xoffset = xoffset
   if width then
      if type(width) == 'number' then
         e:SetWidth(width)
      else
         e.width = width
      end
   end
   if label and label ~= '' then
      e:SetLabel(label)
      --e.label:SetHeight(5)
      e._alignoffset = alignoffset or 2
      e:SetHeight(32)
      e.label:SetPoint('TOPLEFT', 0, 5)
      e.label:SetPoint('TOPRIGHT', 0, 5)
      e.editbox:SetPoint('TOPLEFT',e.frame,'TOPLEFT',7,-10)
   else
      e._alignoffset = alignoffset or 0
   end
   e:SetCallback('OnEnterPressed', function(widget,event,text) widget:ClearFocus(); return enterpressed(self, text, widget) end)
   container:AddChild(e)
   return e
end

function famBagsOptions.Categories:CreateDropDown(container, label, list, multiselect, width, newLine, xoffset, alignoffset, valuechanged)
   local d = AceGUI:Create('Dropdown')
   d:SetText('')
   d.newLine = newLine
   d.xoffset = xoffset
   d:SetWidth(width)
   if label and label ~= '' then
      d:SetLabel(label)
      d._alignoffset = alignoffset or 6
      d:SetHeight(37)
      d.label:SetPoint('TOPLEFT', 0, 5)
      d.label:SetPoint('TOPRIGHT', 0, 5)
      d.dropdown:SetPoint('TOPLEFT',d.frame,'TOPLEFT',-15,-10)
   else
      d:SetHeight(26)
      d._alignoffset = alignoffset or 3
   end
   d.multiselect = multiselect
   if list then
      d:SetList(list)
   end
   if valuechanged then
      d:SetCallback('OnValueChanged', function(widget,event,value,checked) return valuechanged(value, widget, checked) end)
   end

   container:AddChild(d)
   return d
end

function famBagsOptions.Categories:CreateArrowButton(container, dir, newLine, xoffset, alignoffset, onclick)
   local b = AceGUI:Create('ArrowButton')
   b:SetText('')
   b:SetWidth(28)
   b:SetHeight(27)
   b.newLine = newLine
   b.xoffset = xoffset
   b._alignoffset = alignoffset
   self:SetArrowButtonTexture(b, dir)
   if onclick then
      b:SetCallback('OnClick', function(widget,event,mousebutton) return onclick(mousebutton, widget) end)
   end
   container:AddChild(b)
   return b
end

function famBagsOptions.Categories:CreateButton(container, text, width, newLine, xoffset, alignoffset, onclick)
   local b = AceGUI:Create('Button')
   b:SetText(text)
   if width then
      if type(width) == 'number' then
         b:SetWidth(width)
      else
         b.width = width
      end
   end
   b.newLine = newLine
   b.xoffset = xoffset
   b._alignoffset = alignoffset
   if onclick then
      b:SetCallback('OnClick', function(widget,event,mousebutton) return onclick(mousebutton, widget) end)
   end
   b.frame:SetNormalTexture(nil)
   b.frame:SetPushedTexture(nil)
   container:AddChild(b)
   return b
end

function famBagsOptions.Categories:CreateHeader(container, text, width, height)
   local h = AceGUI:Create('Heading')
   h:SetText(text)
   h.width = width or 'fill'
   h:SetHeight(height or 10)
   container:AddChild(h)
   return h
end

function famBagsOptions.Categories:CreateLabel(container, text, fontobject, width, newLine, xoffset, alignoffset)
   local l = AceGUI:Create('Label')
   l:SetText(text)
   l.newLine = newLine
   l.xoffset = xoffset
   if alignoffset then
      l._alignoffset = alignoffset
   end
   if width and type(width) == 'number' then
      l:SetWidth(width)
   end
   l.width = width or 'fill'
   if fontobject then
      l:SetFontObject(fontobject)
      local _, h = fontobject:GetFont()
      l.label:SetHeight(h)
   end
   container:AddChild(l)
   return l
end

function famBagsOptions.Categories:AddCategoryButton(g, text, db)
   CATEGORIES_COUNT = CATEGORIES_COUNT + 1
   local buttonname = ('famBags_CategoryGroupButton_%d'):format(CATEGORIES_COUNT)
   local b = (_G[buttonname] and not _G[buttonname]:IsVisible()) and
                 _G[buttonname] or
                 CreateFrame('Button', buttonname, CATEGORIES_FRAME.content, 'OptionsListButtonTemplate')
   b:SetParent(CATEGORIES_FRAME.content)
   g.buttons = g.buttons or {}
   local bn = #g.buttons + 1
   if bn > 1 then
      b:SetPoint('TOPLEFT', g.buttons[bn-1], 'BOTTOMLEFT', 0, 0)
      b:SetPoint('TOPRIGHT', g.buttons[bn-1], 'BOTTOMRIGHT', 0, 0)
   else
      b:SetPoint('TOPRIGHT', 0, 0)
      b:SetPoint('TOPLEFT', 0, 0)
   end
   b.index = bn
   b.category = text
   db = db or self:GetCategoryDB(text)
   b:SetText((CATEGORY_CONTAINER_COLOR[db.container] or '')..text)
   b:SetScript('OnClick', CategoryOptions_OnClick)
   b:Show()

   g.buttons[bn] = b

   g.content:SetHeight(b:GetHeight() * bn)
   g:FixScroll()

   famBagsOptions.Sections[text] = text
   local name = L['Sections']..' -> '..text
   if not tContains(famBagsOptions.AppearanceCopyFrom, name) then
      table.insert(famBagsOptions.AppearanceCopyFrom, name)
      famBagsOptions.AppearanceCopyFrom_DB[name] = famBags.db.profile.sections[text].bg
      table.insert(famBagsOptions.AppearanceCopyFrom, name..' -> '..L['Title'])
      famBagsOptions.AppearanceCopyFrom_DB[name..' -> '..L['Title'] ] = famBags.db.profile.sections[text].title.bg
      table.sort(famBagsOptions.AppearanceCopyFrom)
   end

 --   button:SetScript('OnDoubleClick', Button_OnDoubleClick)
 --   button:SetScript('OnEnter',Button_OnEnter)
 --   button:SetScript('OnLeave',Button_OnLeave)

   return b
end

function famBagsOptions.Categories:DisableGroup(g, value)
   for i,c in ipairs(g.children) do
      if c.SetDisabled then
         c:SetDisabled(value)
      end
      if c.children and #c.children > 0 then
         self:DisableGroup(c, value)
      end
   end
end

function famBagsOptions.Categories:SetArrowButtonTexture(b, dir)
   local y = (dir == 'Up') and 2 or -9
   local t = b.frame:CreateTexture()
   t:SetTexture('Interface/Buttons/Arrow-'..dir..'-Up')
   t:SetPoint('TOPLEFT', 6, y)
   t:SetPoint('BOTTOMRIGHT', -5, y + 2)
   b.frame:SetNormalTexture(t)
   t = b.frame:CreateTexture()
   t:SetTexture('Interface/Buttons/Arrow-'..dir..'-Down')
   t:SetPoint('TOPLEFT', 3, y)
   t:SetPoint('BOTTOMRIGHT', -8, y + 2)
   b.frame:SetPushedTexture(t)
end

function famBagsOptions.Categories:SetGroupBackdrop(g)
   g.frame:SetBackdrop(PaneBackdrop)
   g.frame:SetBackdropColor(0.1, 0.1, 0.1, 0.5)
   g.frame:SetBackdropBorderColor(0.4, 0.4, 0.4)
end



-- :::::::::::: ExtendedFlow Layout ::::::::::::: -------------------------------------------------------------------------

-- Code from AceGUI-3.0. Need to borrow this code to make safe calls in the implementation of
-- new layouts. Maybe they should fix it so this is not required.
local xpcall = xpcall
local tconcat = table.concat
local math_max = math.max

local function CreateDispatcher(argCount)
   local code = [[
      local xpcall, eh = ...
      local method, ARGS
      local function call() return method(ARGS) end

      local function dispatch(func, ...)
         method = func
         if not method then return end
         ARGS = ...
         return xpcall(call, eh)
      end

      return dispatch
   ]]

   local ARGS = {}
   for i = 1, argCount do ARGS[i] = 'arg'..i end
   code = code:gsub('ARGS', tconcat(ARGS, ', '))
   return assert(loadstring(code, 'safecall Dispatcher['..argCount..']'))(xpcall, errorhandler)
end

local Dispatchers = setmetatable({}, {__index=function(self, argCount)
   local dispatcher = CreateDispatcher(argCount)
   rawset(self, argCount, dispatcher)
   return dispatcher
end})
Dispatchers[0] = function(func)
   return xpcall(func, errorhandler)
end

local function safecall(func, ...)
   return Dispatchers[select('#', ...)](func, ...)
end
-----------------------------------------------------------------------------------------------------

AceGUI:RegisterLayout('ExtendedFlow',
   function(content, children)
      --used height so far
      local height = 0
      --width used in the current row
      local usedwidth = 0
      --height of the current row
      local rowheight = 0
      local rowoffset = 0
      local lastrowoffset

      local width = content.width or content:GetWidth() or 0

      --control at the start of the row
      local rowstart
      local rowstartoffset
      local rowstartxoffset
      local lastrowstart
      local isfullheight
      local xoffset

      local frameoffset
      local lastframeoffset
      local oversize
      local lastchild = 0
      for i = 1, #children do
         local child = children[i]
         if not child._hidden then
            xoffset = child.xoffset or 0
            oversize = nil
            local frame = child.frame
            local frameheight = frame.height or frame:GetHeight() or 0
            local framewidth = frame.width or frame:GetWidth() or 0
            lastframeoffset = frameoffset
            frameoffset = child._alignoffset or child.alignoffset or 0

            if child.width == 'relative' and child.relWidth then
               framewidth = width * child.relWidth
            end

            frame:Show()
            frame:ClearAllPoints()
            if lastchild == 0 then
               frame:SetPoint('TOPLEFT', content)
               rowheight = frameheight
               rowoffset = frameoffset
               rowstart = frame
               rowstartoffset = frameoffset
               rowstartxoffset = xoffset
               usedwidth = framewidth + xoffset
               if usedwidth > width then
                  oversize = true
               end
            else
               if usedwidth == 0 or ((framewidth) + usedwidth > width) or (child.width == 'fill' and not child.forceRow) or child.newLine then
                  if isfullheight then
                     break
                  end
                  rowstart:SetPoint('TOPLEFT', content, 'TOPLEFT', rowstartxoffset, -(height + (rowoffset - rowstartoffset) + 3))
                  height = height + rowheight + 3
                  rowstart = frame
                  rowstartoffset = frameoffset
                  rowstartxoffset = xoffset
                  rowheight = frameheight
                  rowoffset = frameoffset
                  usedwidth = framewidth + xoffset
                  if usedwidth > width then
                     oversize = true
                  end
               else
                  rowoffset = math_max(rowoffset, frameoffset)
                  rowheight = math_max(rowheight, frameheight)

                  frame:SetPoint('TOPLEFT', children[lastchild].frame, 'TOPRIGHT', xoffset, frameoffset - lastframeoffset)
                  if not child.forceRow then
                     usedwidth = framewidth + usedwidth + xoffset
                  else
                     usedwidth = usedwidth + 1
                  end
               end
            end

            if child.width == 'fill' then
               if not child.forceRow then
                  child:SetWidth(width)
                  rowstart = frame
                  rowstartoffset = frameoffset
                  rowoffset = child._alignoffset or child.alignoffset or (rowheight / 2)
                  rowstartoffset = rowoffset
               else
                  child:SetWidth(width - usedwidth)
               end

               frame:SetPoint('RIGHT', content)

               usedwidth = 0

               if child.DoLayout then
                  child:DoLayout()
               end
               rowheight = frame.height or frame:GetHeight() or 0
            elseif child.width == 'relative' then
               if child.relWidth then
                  child:SetWidth(width * child.relWidth)
                  if child.DoLayout then
                     child:DoLayout()
                  end
               end
            elseif oversize then
               if width > 1 then
                  frame:SetPoint('RIGHT', content)
               end
            end

            if child.height == 'fill' then
               frame:SetPoint('BOTTOM', content)
               isfullheight = true
            end
            lastchild = i
            if child.content and not child.content:IsVisible() then
               child.content:Show()
            end
         else
            if child.content then
               child.content:Hide()
            elseif child.frame then
               child.frame:Hide()
            end
         end
      end

      if isfullheight then
         rowstart:SetPoint('TOPLEFT', content, 'TOPLEFT', rowstartxoffset, -height)
      elseif rowstart then
         rowstart:SetPoint('TOPLEFT', content, 'TOPLEFT', rowstartxoffset, -(height + (rowoffset - rowstartoffset) + 3))
      end

      height = height + rowheight + 3
      safecall(content.obj.LayoutFinished, content.obj, nil, height)
   end)
